/*****************************************************************************
 *  Include Section
 *  add all #include here
 *****************************************************************************/
//#include <net.h>
#include "fh_qos_gmac.h"
#include "fh_qos_gmac_phyt.h"
#include <mach/fh_gmac_plat.h>
#include <linux/platform_device.h>
//#include <phy.h>
// #include <asm/arch/hardware.h>
/*****************************************************************************
 * Define section
 * add all #define here
 *****************************************************************************/


/****************************************************************************
 * ADT section
 *  add definition of user defined Data Type that only be used in this file  here
 ***************************************************************************/


/*****************************************************************************
 *  static fun;
 *****************************************************************************/


/*****************************************************************************
 *  extern fun;
 *****************************************************************************/


/*****************************************************************************
 * Global variables section - Local
 * define global variables(will be refered only in this file) here,
 * static keyword should be used to limit scope of local variable to this file
 * e.g.
 *  static uint8_t ufoo;
 *****************************************************************************/
static int __fh_mdio_read(struct dw_qos *pGmac, int phyaddr, int phyreg);
static int __fh_mdio_write(struct dw_qos *pGmac, int phyaddr, int phyreg,
			 u16 phydata);
static int get_phy_id(struct dw_qos *gmac, int addr, u32 *phy_id);
static int check_white_list(__u32 phy_id, __u32 *list, __u32 size);
static int scan_vaild_phy_id(struct dw_qos *gmac,
__u32 *white_list, __u32 list_size, u32 *ret_phy_id);
static int fh_mdio_set_mii(struct mii_bus *bus);
static int __init qos_cmdline_parts_setup(char *s);
static struct phy_reg_cfg g_cmd_parse_phy_cfg = {0};
/*****************************************************************************
 * func below
 *****************************************************************************/
#ifndef PHY_MAX_ADDR
#define PHY_MAX_ADDR	32
#endif

unsigned int phy_support_list[] = {
	FH_GMAC_PHY_IP101G,
	FH_GMAC_PHY_RTL8201,
	FH_GMAC_PHY_TI83848,
	FH_GMAC_PHY_INTERNAL,
	FH_GMAC_PHY_DUMMY,
	FH_GMAC_PHY_INTERNAL_V2,
	FH_GMAC_PHY_RTL8211F,
	FH_GMAC_PHY_MAE0621,
	FH_GMAC_PHY_JL2101,
};

static int __fh_mdio_read(struct dw_qos *pGmac, int phyaddr, int phyreg)
{
#ifdef CONFIG_EMULATION
	return 0xe3ff;
#else
	u32 ret;
	int i = 1000;

	dw_writel(pGmac, mac.mdio_addr,
			0x3 << 2 | phyaddr << 21 | 1 << 8 | phyreg << 16 | 0x1);
	while ((dw_readl(pGmac, mac.mdio_addr) & 0x1) && i) {
		i--;
		udelay(100);
	}
	if (!i) {
		pr_err("ERROR: %s, timeout, phyaddr: 0x%x, phyreg: 0x%x\n",
		__func__, phyaddr, phyreg);
		return 0;
	}
	ret  = dw_readl(pGmac, mac.mdio_data);
	//pr_err("[read] :: reg:%08x = %08x\n",phyreg, ret);
	return ret;
#endif

}

static int __fh_mdio_write(struct dw_qos *pGmac, int phyaddr, int phyreg,
			 u16 phydata)
{

#ifdef CONFIG_EMULATION
	return 0;
#else
	//pr_err("[write] :: reg:%08x = %08x\n",phyreg, phydata);
	int i = 1000;

	dw_writel(pGmac, mac.mdio_data, phydata);
	dw_writel(pGmac, mac.mdio_addr, 0x1 << 2 | phyaddr << 21 | 1 << 8
					| phyreg << 16 | 0x1);
	while ( (dw_readl(pGmac, mac.mdio_addr) & 0x1) && i) {
		udelay(100);
		i--;
	}
	if (!i) {
		pr_err("ERROR: %s, timeout, phyaddr: %d, phyreg: 0x%x, phydata: 0x%x\n",
				__func__, phyaddr, phyreg, phydata);
	}
	return 0;
#endif

}

static int get_phy_id(struct dw_qos *gmac, int addr, u32 *phy_id)
{
	__u16 phy_reg;

	phy_reg = __fh_mdio_read(gmac, addr, 2);
	*phy_id = (phy_reg & 0xffff) << 16;
	phy_reg = __fh_mdio_read(gmac, addr, 3);
	*phy_id |= (phy_reg & 0xffff);

	return 0;
}


static int check_white_list(__u32 phy_id, __u32 *list, __u32 size)
{
	int i;
	int ret = -1;

	for (i = 0; i < size; i++) {
		if (phy_id == list[i]) {
			ret = 0;
			break;
		}
	}
	return ret;
}

/* 0 find vaild, others failed*/
static int scan_vaild_phy_id(struct dw_qos *gmac,
__u32 *white_list, __u32 list_size, u32 *ret_phy_id)
{
	__u32 phy_id = 0;
	int i;
	int ret;
	*ret_phy_id = 0xffffffff;
	//add 2 dummy read....
	get_phy_id(gmac, 0, &phy_id);
	get_phy_id(gmac, 0, &phy_id);

	for (i = 0; i < PHY_MAX_ADDR; i++) {
		ret = get_phy_id(gmac, i, &phy_id);
		if (ret == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) {
			if (check_white_list(phy_id, white_list, list_size))
				continue;
			break;
		}
	}
	if (i == PHY_MAX_ADDR)
		return -1;
	*ret_phy_id = phy_id;
	return 0;
}


int fh_phy_reset_api(struct phy_device *phydev)
{
	struct net_device *ndev;
	struct dw_qos *pGmac;

	pr_err("reset api get in....\n");
	ndev = phydev->mdio.bus->priv;
	pGmac = netdev_priv(ndev);

	if (pGmac->phy_sel)
		pGmac->phy_sel(pGmac->ac_phy_info->phy_sel);
	if (pGmac->inf_set)
		pGmac->inf_set(pGmac->ac_phy_info->phy_inf);
	if (pGmac->ac_phy_info->phy_reset)
		pGmac->ac_phy_info->phy_reset();

	fh_mdio_set_mii(pGmac->mii);
	return 0;
}
EXPORT_SYMBOL(fh_phy_reset_api);


int fh_phy_triger_api(struct phy_device *phydev)
{
	struct net_device *ndev;
	int ret;
	struct dw_qos *pGmac;

	ndev = phydev->mdio.bus->priv;
	pGmac = netdev_priv(ndev);
	if (pGmac->ac_phy_info->phy_triger) {
		ret = pGmac->ac_phy_info->phy_triger();
		if (ret != PHY_TRIGER_OK) {
			pr_err("got triger evet [%x]\n", ret);
			if (pGmac->phy_sel)
				pGmac->phy_sel(pGmac->ac_phy_info->phy_sel);
			if (pGmac->inf_set)
				pGmac->inf_set(pGmac->ac_phy_info->phy_inf);
			if (pGmac->ac_phy_info->phy_reset)
				pGmac->ac_phy_info->phy_reset();

			fh_mdio_set_mii(pGmac->mii);
		}
	}
	return 0;
}
EXPORT_SYMBOL(fh_phy_triger_api);

void cpy_dl_cmd_info(struct phy_reg_cfg *p_cfg){

	p_cfg->dll_enable = g_cmd_parse_phy_cfg.dll_enable;

	p_cfg->tx_10_dll_val = g_cmd_parse_phy_cfg.tx_10_dll_val;
	p_cfg->rx_10_dll_val = g_cmd_parse_phy_cfg.rx_10_dll_val;
	p_cfg->tx_10_polarity = g_cmd_parse_phy_cfg.tx_10_polarity;
	p_cfg->rx_10_polarity = g_cmd_parse_phy_cfg.rx_10_polarity;

	p_cfg->tx_100_dll_val = g_cmd_parse_phy_cfg.tx_100_dll_val;
	p_cfg->rx_100_dll_val = g_cmd_parse_phy_cfg.rx_100_dll_val;
	p_cfg->tx_100_polarity = g_cmd_parse_phy_cfg.tx_100_polarity;
	p_cfg->rx_100_polarity = g_cmd_parse_phy_cfg.rx_100_polarity;

	p_cfg->tx_1000_dll_val = g_cmd_parse_phy_cfg.tx_1000_dll_val;
	p_cfg->rx_1000_dll_val = g_cmd_parse_phy_cfg.rx_1000_dll_val;
	p_cfg->tx_1000_polarity = g_cmd_parse_phy_cfg.tx_1000_polarity;
	p_cfg->rx_1000_polarity = g_cmd_parse_phy_cfg.rx_1000_polarity;

}

void bind_phy_reg_cfg(struct dw_qos *pGmac, int phy_id){
	struct phy_reg_cfg *p_cfg;

	p_cfg = (struct phy_reg_cfg *)pGmac->priv_data->p_cfg_array;

	for (; p_cfg->id != 0; p_cfg++) {

		if (p_cfg->id == g_cmd_parse_phy_cfg.id){
			//cpy cmdline setting...
			//pr_err("got cmd line setting...\n");
			cpy_dl_cmd_info(p_cfg);
		}
		if (p_cfg->id == phy_id) {
			/*pr_err("find phy id [%08x] reg para\n",p_cfg->id);*/
			//bind phy set list here..
			pGmac->ac_reg_cfg = p_cfg;
			//set phy ref work clk.
			if (pGmac->ac_phy_info->ex_sync_mac_spd) {
				pGmac->ac_phy_info->ex_sync_mac_spd(100,
				pGmac->ac_reg_cfg);
			}
		}
	}
	if(!pGmac->ac_reg_cfg)
		BUG_ON(1);
}

int auto_find_phy(struct net_device *ndev)
{
	int i;
	int ret = -1;
	int ret_phy_id;
	struct fh_gmac_platform_data *p_info;
	struct platform_device *pdev;
	struct phy_interface_info *p_phy_info;
	struct dw_qos *pGmac = netdev_priv(ndev);

	pdev = pGmac->pdev;
	p_info  = (struct fh_gmac_platform_data *)pdev->dev.platform_data;
	if (!p_info) {
		pr_err("NULL plat info ....\n");
		return -1;
	}
	for (i = 0, p_phy_info = p_info->phy_info;
	i < ARRAY_SIZE(p_info->phy_info); i++) {
		//only parse usr set info.if not set ,just continue.
		if (!!p_phy_info[i].phy_sel) {
			//2:phy sel
			if (p_info->phy_sel)
				p_info->phy_sel(p_phy_info[i].phy_sel);
			//3:phy reset
			if (p_phy_info[i].phy_reset)
				p_phy_info[i].phy_reset();
			//4:mac interface set mii or rmii...sw set later,when phy auto done...
#if (0)
			if (p_info->inf_set)
				p_info->inf_set(p_phy_info[i].phy_inf);
#endif
			/* find phy id.. */
			ret = scan_vaild_phy_id(pGmac, phy_support_list,
			ARRAY_SIZE(phy_support_list), &ret_phy_id);
			if (!ret)
				break;
		}
	}
	if (!ret) {
		//find vaild phy...just parse
		pGmac->ac_phy_info = &p_phy_info[i];
		bind_phy_reg_cfg(pGmac, ret_phy_id);
		pGmac->phy_sel = p_info->phy_sel;
		pGmac->inf_set = p_info->inf_set;
		pGmac->phy_interface = p_phy_info[i].phy_inf;
		pr_err("auto find phy info :: %s : \n",
		p_phy_info[i].phy_sel == EXTERNAL_PHY ?
		"external phy" : "internal phy");

		if(p_phy_info[i].phy_inf == PHY_INTERFACE_MODE_MII)
			pr_err(" mii\n");
		if(p_phy_info[i].phy_inf == PHY_INTERFACE_MODE_RMII)
			pr_err(" rmii\n");
		if (p_phy_info[i].phy_inf == PHY_INTERFACE_MODE_RGMII)
			pr_err(" rgmii\n");

		//sync with net core later...
		return 0;
	}
	return -1;

}

static int fh_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
{
	struct net_device *ndev = bus->priv;
	struct dw_qos *pGmac = netdev_priv(ndev);

	return __fh_mdio_read(pGmac, phyaddr, phyreg);
}

static int fh_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg,
			 u16 phydata)
{
	struct net_device *ndev = bus->priv;
	struct dw_qos *pGmac = netdev_priv(ndev);

#ifdef CONFIG_INTERNAL_PHY_RESET
	__u32 phy_id = 0;

	if ((phyreg == 0) && (phydata & (1 << 15))) {
		get_phy_id(pGmac, 0, &phy_id);
		get_phy_id(pGmac, 0, &phy_id);

		if (phy_id == FH_GMAC_PHY_INTERNAL_V2) {
			pGmac->ac_phy_info->phy_reset();
			return 0;
		}
	}
#endif

	return __fh_mdio_write(pGmac, phyaddr, phyreg, phydata);
}



int fh_mdio_reset(struct mii_bus *bus)
{

	return 0;
}

static int fh_mdio_set_mii(struct mii_bus *bus)
{
	__u32 ret;
	struct phy_reg_cfg *p_cfg;
	struct phy_reg_cfg_list *p_cfg_data;
	struct net_device *ndev = bus->priv;
	struct dw_qos *pGmac = netdev_priv(ndev);

	int phyid = pGmac->phy_id;

	if (pGmac->phydev == NULL)
		return -ENODEV;

#ifdef CONFIG_EMULATION
		return 0;
#endif

	p_cfg = (struct phy_reg_cfg *)pGmac->priv_data->p_cfg_array;
	for (; p_cfg->id != 0; p_cfg++) {
		if (p_cfg->id == pGmac->phydev->phy_id) {
			for (p_cfg_data = p_cfg->list;
			p_cfg_data->r_w != 0; p_cfg_data++) {
				if ((p_cfg_data->r_w & ACTION_MASK) == W_PHY)
					fh_mdio_write(bus, phyid,
					p_cfg_data->reg_add,
					p_cfg_data->reg_val);
				else if ((p_cfg_data->r_w & ACTION_MASK)
				== M_PHY) {
					ret = fh_mdio_read(bus, phyid,
					p_cfg_data->reg_add);
					ret &= ~(p_cfg_data->reg_mask);
					ret |= (p_cfg_data->reg_val &
					p_cfg_data->reg_mask);
					fh_mdio_write(bus, phyid,
					p_cfg_data->reg_add, ret);
				}
			}
			/*find one match;return ok*/
			return 0;
		}
	}
	return -ENODEV;
}

void fh_qos_find_vaild_phy_busid(struct net_device *ndev)
{
	struct dw_qos *pGmac = netdev_priv(ndev);
	//here set bus id is broadcast id...
	//but mdio suppoty multi phyid...maybe use phyid array is better.
	pGmac->phy_id = 0;
}

static int fh_qos_phy_connect(struct net_device *ndev)
{
	struct dw_qos *pGmac;
	char phy_id[MII_BUS_ID_SIZE + 3];
	struct phy_device *phydev = NULL;

	pGmac = netdev_priv(ndev);
	snprintf(phy_id, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, pGmac->mii->id, 0);
	phydev = phy_connect(pGmac->ndev,
				phy_id,
				&fh_qos_adjust_link,
				pGmac->phy_interface);
	if (!phydev) {
		netdev_err(ndev, "no PHY found\n");
		return -1;
	}

	if (netif_msg_probe(pGmac))
		phy_attached_info(phydev);

	pGmac->link    = 0;
	pGmac->speed   = 0;
	pGmac->duplex  = DUPLEX_UNKNOWN;
	pGmac->phydev = phydev;
	return 0;
}

int fh_qos_mdio_register(struct net_device *ndev)
{
	struct dw_qos *pGmac = netdev_priv(ndev);
	struct mii_bus *new_bus;
	int err = 0;

	new_bus = mdiobus_alloc();
	if (!new_bus)
		return -ENOMEM;
	new_bus->name =
	    pGmac->phy_interface ==
	    PHY_INTERFACE_MODE_MII ? "qos_mii" : "qos_rmii";
	new_bus->read = &fh_mdio_read;
	new_bus->write = &fh_mdio_write;
	new_bus->reset = &fh_mdio_reset;
	new_bus->priv = ndev;
	new_bus->parent = pGmac->dev;
	snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s", pGmac->common_irq_name);
	err = mdiobus_register(new_bus);
	if (err != 0) {
		pr_err("%s: Cannot register as MDIO bus, error: %d\n",
		       new_bus->name, err);
		goto bus_register_free;
	}

	pGmac->mii = new_bus;

	fh_qos_find_vaild_phy_busid(ndev);
	//connect..
	err = fh_qos_phy_connect(ndev);
	if (err != 0) {
		pr_err("%s: phy connect failed, error: %d\n",
		       new_bus->name, err);
		goto bus_register_fail;
	}
	err = fh_mdio_set_mii(pGmac->mii);
	if (err != 0) {
		pr_err("%s: phy init failed, error: %d\n",
		       new_bus->name, err);
		goto bus_register_fail;
	}
	return 0;

bus_register_fail:
	mdiobus_unregister(new_bus);
bus_register_free:
	pGmac->phydev = NULL;
	mdiobus_free(new_bus);
	return err;
}

int fh_qos_mdio_unregister(struct net_device *ndev)
{
	struct dw_qos *pGmac = netdev_priv(ndev);

	mdiobus_unregister(pGmac->mii);
	mdiobus_free(pGmac->mii);
	pGmac->mii->priv = NULL;
	pGmac->phydev = NULL;
	return 0;

}



__be32 qos_in_aton(const char *str)
{
	unsigned long l;
	unsigned int val;
	int i;

	l = 0;
	for (i = 0; i < 4; i++)	{
		l <<= 8;
		if (*str != '\0') {
			val = 0;
			while (*str != '\0' && *str != '.' && *str != '\n') {
				val *= 10;
				val += *str - '0';
				str++;
			}
			l |= val;
			if (*str != '\0')
				str++;
		}
	}
	return htonl(l);
}

static int __init qos_cmdline_parts_setup(char *s)
{
	char *cp, *raw;
	int num = 0;
	u32 phy_id = 0;
	u32 tmp_dl = 0;

	raw = s;
	while (raw && *raw) {
		if ((cp = strchr(raw, ':')))
			*cp++ = '\0';
		if (strlen(raw) > 0) {
			//pr_err("dll parse: Parameter `%s'\n", raw);
			switch (num) {
			case 0:
			phy_id = simple_strtoul(raw, NULL, 16);
			//pr_err("parse phy id is %x\n",phy_id);
			g_cmd_parse_phy_cfg.id = phy_id;
			break;

			case 1:
			//pr_err("if enable dll\n");
			if (strcmp(raw, "enable") == 0) {
				//pr_err("enable dll setting...\n");
				g_cmd_parse_phy_cfg.dll_enable =
				DLL_SETTING_ENABLE;
			}
			break;

			case 2:
			tmp_dl = qos_in_aton(raw);
			//tmp_dl = rx dl | rx pol | tx dl | tx pol
			g_cmd_parse_phy_cfg.rx_1000_dll_val =
			tmp_dl >> 24;
			g_cmd_parse_phy_cfg.rx_1000_polarity =
			((tmp_dl >> 16) & 0xff);
			g_cmd_parse_phy_cfg.tx_1000_dll_val =
			((tmp_dl >> 8) & 0xff);
			g_cmd_parse_phy_cfg.tx_1000_polarity =
			((tmp_dl) & 0xff);
			pr_err("1000M :: tx pol : tx dl : rx pol : rx dl = %d : %d : %d : %d\n",
			g_cmd_parse_phy_cfg.tx_1000_polarity,
			g_cmd_parse_phy_cfg.tx_1000_dll_val,
			g_cmd_parse_phy_cfg.rx_1000_polarity,
			g_cmd_parse_phy_cfg.rx_1000_dll_val);
			break;

			case 3:
			tmp_dl = qos_in_aton(raw);
			g_cmd_parse_phy_cfg.rx_100_dll_val =
			tmp_dl >> 24;
			g_cmd_parse_phy_cfg.rx_100_polarity =
			((tmp_dl >> 16) & 0xff);
			g_cmd_parse_phy_cfg.tx_100_dll_val =
			((tmp_dl >> 8) & 0xff);
			g_cmd_parse_phy_cfg.tx_100_polarity =
			((tmp_dl) & 0xff);
			pr_err("100M :: tx pol : tx dl : rx pol : rx dl = %d : %d : %d : %d\n",
			g_cmd_parse_phy_cfg.tx_100_polarity,
			g_cmd_parse_phy_cfg.tx_100_dll_val,
			g_cmd_parse_phy_cfg.rx_100_polarity,
			g_cmd_parse_phy_cfg.rx_100_dll_val);
			break;

			case 4:
			tmp_dl = qos_in_aton(raw);
			g_cmd_parse_phy_cfg.rx_10_dll_val =
			tmp_dl >> 24;
			g_cmd_parse_phy_cfg.rx_10_polarity =
			((tmp_dl >> 16) & 0xff);
			g_cmd_parse_phy_cfg.tx_10_dll_val =
			((tmp_dl >> 8) & 0xff);
			g_cmd_parse_phy_cfg.tx_10_polarity =
			((tmp_dl) & 0xff);
			pr_err("10M :: tx pol : tx dl : rx pol : rx dl = %d : %d : %d : %d\n",
			g_cmd_parse_phy_cfg.tx_10_polarity,
			g_cmd_parse_phy_cfg.tx_10_dll_val,
			g_cmd_parse_phy_cfg.rx_10_polarity,
			g_cmd_parse_phy_cfg.rx_10_dll_val);
			break;
			}
		}
		num++;
		raw = cp;
	}

	return 1;
}
__setup("qos_dll=", qos_cmdline_parts_setup);
